﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

namespace Lookstone.Utility
{
	public enum HashMode : byte
	{
		HMACSHA1,
		MD5,
		SHA1,
		SHA256,
		SHA384,
		SHA512
	}

	public class HashProvider
	{
		public static string ComputeHash(string valueToHash, string salt, HashMode algorithm = HashMode.SHA256)
		{
			if (string.IsNullOrEmpty(salt))
				throw new ArgumentOutOfRangeException("Salt is required!");

			byte[] saltBytes = Encoding.UTF8.GetBytes(salt);

			return ComputeHash(valueToHash, saltBytes, algorithm);
		}

		public static string ComputeHash(string valueToHash, byte[] salt, HashMode algorithm = HashMode.SHA256)
		{
			if (salt == null || salt.Length == 0)
				throw new ArgumentOutOfRangeException("Salt is required!");

			byte[] valueBytes = Encoding.UTF8.GetBytes(valueToHash);

			List<byte> saltedValue = new List<byte>();
			saltedValue.AddRange(valueBytes);

			// only add salt if we're not using a KeyHash
			if (algorithm != HashMode.HMACSHA1)
				saltedValue.AddRange(salt);

			HashAlgorithm hash = null;

			switch (algorithm)
			{
				case HashMode.HMACSHA1:
					hash = new HMACSHA1(salt);
					break;
				case HashMode.MD5:
					hash = new MD5CryptoServiceProvider();
					break;
				case HashMode.SHA1:
					hash = new SHA1Managed();
					break;
				case HashMode.SHA256:
					hash = new SHA256Managed();
					break;
				case HashMode.SHA384:
					hash = new SHA384Managed();
					break;
				case HashMode.SHA512:
					hash = new SHA512Managed();
					break;
			}

			byte[] hashedValue = hash.ComputeHash(saltedValue.ToArray());

			return System.Convert.ToBase64String(hashedValue);
		}

		/// <summary>
		/// Generates a cryptographically secure random salt of specified size.
		/// </summary>
		/// <param name="sizeInBytes">Size of salt to generate; if &lt;= 0, defaults to 16 bytes</param>
		/// <returns>Base64 encoded salt</returns>
		public static string GenerateSalt(short sizeInBytes = 16)
		{
			byte[] saltInBytes = new byte[sizeInBytes >= 0 ? sizeInBytes : 16];

			RNGCryptoServiceProvider saltGenerator = new RNGCryptoServiceProvider();
			saltGenerator.GetNonZeroBytes(saltInBytes);

			return System.Convert.ToBase64String(saltInBytes);
		}

		/// <summary>
		/// Takes a user-provided value, a previously hashed value, a salt, and hashing algorithm and compares for equality
		/// </summary>
		/// <param name="valueToHash">User-provided value to hash and compare</param>
		/// <param name="valueToCompare">Previously hashed value to compare</param>
		/// <param name="salt">Salt used to hash previous and new values</param>
		/// <param name="algorithm">Hashing algorithm to use</param>
		/// <returns>True if hash matches, false otherwise</returns>
		public static bool VerifyHash(string valueToHash, string valueToCompare, string salt, HashMode algorithm = HashMode.SHA256)
		{
			byte[] saltBytes = Encoding.UTF8.GetBytes(salt);

			return VerifyHash(valueToHash, valueToCompare, saltBytes, algorithm);
		}

		/// <summary>
		/// Takes a user-provided value, a previously hashed value, a salt, and hashing algorithm and compares for equality
		/// </summary>
		/// <param name="valueToHash">User-provided value to hash and compare</param>
		/// <param name="valueToCompare">Previously hashed value to compare</param>
		/// <param name="salt">Salt used to hash previous and new values</param>
		/// <param name="algorithm">Hashing algorithm to use</param>
		/// <returns>True if hash matches, false otherwise</returns>
		public static bool VerifyHash(string valueToHash, string valueToCompare, byte[] salt, HashMode algorithm = HashMode.SHA256)
		{
			string newHash = ComputeHash(valueToHash, salt, algorithm);

			return string.Compare(newHash, valueToCompare) == 0;
		}
	}
}
